package com.user.mediacoderdemo;

import android.content.Context;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.TimedText;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.Surface;

import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Map;


/**
 * Created by zoudong on 2017/2/11.
 */

public class VideoFramePlayer  implements IVideoFramePlayer{
    public static final String TAG = "VideoFramePlayer";
    private MediaExtractor mediaExtractor;
    private MediaFormat format;
    public static final String VIDEO_MIME = "video/avc";
    private boolean isloop=true; //是否循环
    private MediaDecodec decodec;
    private WorkDecoder workDecoder;

    public  void setDataSource(@NonNull Context context, @NonNull Uri uri, @Nullable Map<String, String> headers) throws IOException {
        reset();
        mediaExtractor = new MediaExtractor();
        mediaExtractor.setDataSource(context, uri, headers);
        format=createMediaFormt();
        setVideoSizeChanged();
    }
    public  void setDataSource(@NonNull String path, @Nullable Map<String, String> headers) throws IOException {
        reset();
        mediaExtractor = new MediaExtractor();
        mediaExtractor.setDataSource(path, headers);
        format=createMediaFormt();
        setVideoSizeChanged();
    }

    public  void setDataSource(@NonNull String path) throws IOException {
        reset();
        mediaExtractor = new MediaExtractor();
        mediaExtractor.setDataSource(path);
        format=createMediaFormt();
        setVideoSizeChanged();
    }
    public void setDataSource( @NonNull FileDescriptor fd, long offset, long length) throws IOException{
        reset();
        mediaExtractor = new MediaExtractor();
        mediaExtractor.setDataSource(fd, offset, length);
        format=createMediaFormt();
        setVideoSizeChanged();
    }

    public  void reset() {
            stop();
            release();
    }



    public void configure(MediaFormat format,Surface surface) {
        decodec = new MediaDecodec(VIDEO_MIME);
        decodec.setOnPlayeringUpdateListener(new AbsMediaCodecWraper.OnPlayeringUpdateListener() {
            @Override
            public void onBufferingUpdate(AbsMediaCodecWraper mp, long currtime, long totaltime) {
                Log.e("zoudong", "onBufferingUpdate====" + "mp = [" + mp + "], currtime = [" + currtime + "], totaltime = [" + totaltime + "]");
            }
        });
        decodec.setOnBufferingUpdateListener(new AbsMediaCodecWraper.OnBufferingUpdateListener() {
            @Override
            public void onBufferingUpdate(AbsMediaCodecWraper mp, int percent) {
                Log.e("zoudong", "onBufferingUpdate====" + "mp = [" + mp + "], percent = [" + percent + "]");
            }
        });
        decodec.setOnCompletionListener(new AbsMediaCodecWraper.OnPlayeringUpdateListener() {
            @Override
            public void onBufferingUpdate(AbsMediaCodecWraper mp, long currtime, long totaltime) {
                Log.e("zoudong", "onBufferingUpdate====" + "mp = [" + mp + "], currtime = [" + currtime + "], totaltime = [" + totaltime + "]");
            }
        });
        decodec.configure(format,surface,null,0);
    }
    public void configure(int width, int height, ByteBuffer csd0,Surface surface) {
        Log.d(TAG,"config:" + csd0.limit());
        MediaFormat format = MediaFormat.createVideoFormat(VIDEO_MIME, width, height);
        format.setByteBuffer("csd-0", csd0);
        configure(format,surface);
    }
    private MediaFormat createMediaFormt() {
        return VideoInfoKit.getMediaFormat("video/", mediaExtractor);
    }

    @Override
    public void prepare() {
        if (format == null) {
            setError(1, 1);
            return;
        }
        decodec.start();
        setPrepared();
    }
    @Override
    public  void  start(){
        if (workDecoder==null) { //开始
            _start();
        } else if (!workDecoder.isRuning()) { // 开始
            workDecoder.start();
        } else {
            throw new RuntimeException(" this video  is already running");
        }
    }

    private void _start() {
        workDecoder = new WorkDecoder(decodec, mediaExtractor, isloop);
        workDecoder.start();
    }

    @Override
    public void seek(long time) {
        mediaExtractor.seekTo(20000000 , MediaExtractor.SEEK_TO_CLOSEST_SYNC);
        decodec.flush();
    }
    boolean isPlaying(){
        if (workDecoder!=null) {
            return workDecoder.isRuning();
        }
        return false;
    }
    @Override
    public void pause() {
        if (isPlaying()) {
            workDecoder.pause();
        }
    }

    @Override
    public void stop() {
        if (workDecoder!=null) {
            workDecoder.stop();
            workDecoder=null;
        }

    }

    @Override
    public void release() {
        if (mediaExtractor != null) {
            mediaExtractor.release();
            mediaExtractor=null;
        }
        if (decodec != null) {
            decodec.stop();
            decodec.release();
            decodec=null;
        }
    }

    private void setVideoSizeChanged() {
        if (format == null) {
            setError(1, 1);
            return;
        }
        if (mOnVideoSizeChangedListener != null) {
            mOnVideoSizeChangedListener.onVideoSizeChanged(this, format);
        }
    }

    private void setPrepared() {
       
        if (mOnPreparedListener != null) {
            mOnPreparedListener.onPrepared(this);
        }
    }

    private void setError(int what, int extra) {
        if (mOnErrorListener != null) {
            mOnErrorListener.onError(this, what, extra);
        }
    }

    /**
     * Interface definition for a callback to be invoked when the media
     * source is ready for playback.
     */
    public interface OnPreparedListener
    {
        /**
         * Called when the media file is ready for playback.
         *
         * @param mp the IVideoFramePlayer that is ready for playback
         */
        void onPrepared(IVideoFramePlayer mp);
    }

    /**
     * Register a callback to be invoked when the media source is ready
     * for playback.
     *
     * @param listener the callback that will be run
     */
    public void setOnPreparedListener(OnPreparedListener listener)
    {
        mOnPreparedListener = listener;
    }

    private OnPreparedListener mOnPreparedListener;

    /**
     * Interface definition for a callback to be invoked when playback of
     * a media source has completed.
     */
    public interface OnCompletionListener
    {
        /**
         * Called when the end of a media source is reached during playback.
         *
         * @param mp the IVideoFramePlayer that reached the end of the file
         */
        void onCompletion(IVideoFramePlayer mp);
    }

    /**
     * Register a callback to be invoked when the end of a media source
     * has been reached during playback.
     *
     * @param listener the callback that will be run
     */
    public void setOnCompletionListener(OnCompletionListener listener)
    {
        mOnCompletionListener = listener;
    }

    private OnCompletionListener mOnCompletionListener;

    /**
     * Interface definition of a callback to be invoked indicating buffering
     * status of a media resource being streamed over the network.
     */
    public interface OnBufferingUpdateListener
    {
        /**
         * Called to update status in buffering a media stream received through
         * progressive HTTP download. The received buffering percentage
         * indicates how much of the content has been buffered or played.
         * For example a buffering update of 80 percent when half the content
         * has already been played indicates that the next 30 percent of the
         * content to play has been buffered.
         *
         * @param mp      the IVideoFramePlayer the update pertains to
         * @param percent the percentage (0-100) of the content
         *                that has been buffered or played thus far
         */
        void onBufferingUpdate(IVideoFramePlayer mp, int percent);
    }

    /**
     * Register a callback to be invoked when the status of a network
     * stream's buffer has changed.
     *
     * @param listener the callback that will be run.
     */
    public void setOnBufferingUpdateListener(OnBufferingUpdateListener listener)
    {
        mOnBufferingUpdateListener = listener;
    }

    private OnBufferingUpdateListener mOnBufferingUpdateListener;

    /**
     * Interface definition of a callback to be invoked indicating
     * the completion of a seek operation.
     */
    public interface OnSeekCompleteListener
    {
        /**
         * Called to indicate the completion of a seek operation.
         *
         * @param mp the IVideoFramePlayer that issued the seek operation
         */
        public void onSeekComplete(IVideoFramePlayer mp);
    }

    /**
     * Register a callback to be invoked when a seek operation has been
     * completed.
     *
     * @param listener the callback that will be run
     */
    public void setOnSeekCompleteListener(OnSeekCompleteListener listener)
    {
        mOnSeekCompleteListener = listener;
    }

    private OnSeekCompleteListener mOnSeekCompleteListener;

    public interface OnVideoSizeChangedListener
    {
        public void onVideoSizeChanged(IVideoFramePlayer mp, MediaFormat format);
    }

    /**
     * Register a callback to be invoked when the video size is
     * known or updated.
     *
     * @param listener the callback that will be run
     */
    public void setOnVideoSizeChangedListener(OnVideoSizeChangedListener listener)
    {
        mOnVideoSizeChangedListener = listener;
    }

    private OnVideoSizeChangedListener mOnVideoSizeChangedListener;


    /** Unspecified media player info.
     * @see android.media.MediaPlayer.OnInfoListener
     */
    public static final int MEDIA_INFO_UNKNOWN = 1;

    /** The player was started because it was used as the next player for another
     * player, which just completed playback.
     * @see android.media.MediaPlayer.OnInfoListener
     * @hide
     */
    public static final int MEDIA_INFO_STARTED_AS_NEXT = 2;

    /** The player just pushed the very first video frame for rendering.
     * @see android.media.MediaPlayer.OnInfoListener
     */
    public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3;

    /** The video is too complex for the decoder: it can't decode frames fast
     *  enough. Possibly only the audio plays fine at this stage.
     * @see android.media.MediaPlayer.OnInfoListener
     */
    public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;

    /** MediaPlayer is temporarily pausing playback internally in order to
     * buffer more data.
     * @see android.media.MediaPlayer.OnInfoListener
     */
    public static final int MEDIA_INFO_BUFFERING_START = 701;

    /** MediaPlayer is resuming playback after filling buffers.
     * @see android.media.MediaPlayer.OnInfoListener
     */
    public static final int MEDIA_INFO_BUFFERING_END = 702;

    /** Estimated network bandwidth information (kbps) is available; currently this event fires
     * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END}
     * when playing network files.
     * @see android.media.MediaPlayer.OnInfoListener
     * @hide
     */
    public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703;

    /** Bad interleaving means that a media has been improperly interleaved or
     * not interleaved at all, e.g has all the video samples first then all the
     * audio ones. Video is playing but a lot of disk seeks may be happening.
     * @see android.media.MediaPlayer.OnInfoListener
     */
    public static final int MEDIA_INFO_BAD_INTERLEAVING = 800;

    /** The media cannot be seeked (e.g live stream)
     * @see android.media.MediaPlayer.OnInfoListener
     */
    public static final int MEDIA_INFO_NOT_SEEKABLE = 801;

    /** A new set of metadata is available.
     * @see android.media.MediaPlayer.OnInfoListener
     */
    public static final int MEDIA_INFO_METADATA_UPDATE = 802;

    /** A new set of external-only metadata is available.  Used by
     *  JAVA framework to avoid triggering track scanning.
     * @hide
     */
    public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803;

    /** Failed to handle timed text track properly.
     * @see android.media.MediaPlayer.OnInfoListener
     *
     * {@hide}
     */
    public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900;

    /** Subtitle track was not supported by the media framework.
     * @see android.media.MediaPlayer.OnInfoListener
     */
    public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;

    /** Reading the subtitle track takes too long.
     * @see android.media.MediaPlayer.OnInfoListener
     */
    public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;

    public interface OnInfoListener
    {
        /**
         * Called to indicate an info or a warning.
         *
         * @param mp      the MediaPlayer the info pertains to.
         * @param what    the type of info or warning.
         * <ul>
         * <li>{@link #MEDIA_INFO_UNKNOWN}
         * <li>{@link #MEDIA_INFO_VIDEO_TRACK_LAGGING}
         * <li>{@link #MEDIA_INFO_VIDEO_RENDERING_START}
         * <li>{@link #MEDIA_INFO_BUFFERING_START}
         * <li>{@link #MEDIA_INFO_BUFFERING_END}
         * <li><code>MEDIA_INFO_NETWORK_BANDWIDTH (703)</code> -
         *     bandwidth information is available (as <code>extra</code> kbps)
         * <li>{@link #MEDIA_INFO_BAD_INTERLEAVING}
         * <li>{@link #MEDIA_INFO_NOT_SEEKABLE}
         * <li>{@link #MEDIA_INFO_METADATA_UPDATE}
         * <li>{@link #MEDIA_INFO_UNSUPPORTED_SUBTITLE}
         * <li>{@link #MEDIA_INFO_SUBTITLE_TIMED_OUT}
         * </ul>
         * @param extra an extra code, specific to the info. Typically
         * implementation dependent.
         * @return True if the method handled the info, false if it didn't.
         * Returning false, or not having an OnErrorListener at all, will
         * cause the info to be discarded.
         */
        boolean onInfo(IVideoFramePlayer mp, int what, int extra);
    }

    /**
     * Register a callback to be invoked when an info/warning is available.
     *
     * @param listener the callback that will be run
     */
    public void setOnInfoListener(OnInfoListener listener)
    {
        mOnInfoListener = listener;
    }

    private OnInfoListener mOnInfoListener;

    
    /**
     * Interface definition of a callback to be invoked when a
     * timed text is available for display.
     */
    public interface OnTimedTextListener
    {
        /**
         * Called to indicate an avaliable timed text
         *
         * @param mp             the IVideoFramePlayer associated with this callback
         * @param text           the timed text sample which contains the text
         *                       needed to be displayed and the display format.
         */
        public void onTimedText(IVideoFramePlayer mp, TimedText text);
    }

    /**
     * Register a callback to be invoked when a timed text is available
     * for display.
     *
     * @param listener the callback that will be run
     */
    public void setOnTimedTextListener(OnTimedTextListener listener)
    {
        mOnTimedTextListener = listener;
    }

    private OnTimedTextListener mOnTimedTextListener;

    public interface OnErrorListener
    {
        /**
         * Called to indicate an error.
         *
         * @param mp      the MediaPlayer the error pertains to
         * @param what    the type of error that has occurred:
         * @return True if the method handled the error, false if it didn't.
         * Returning false, or not having an OnErrorListener at all, will
         * cause the OnCompletionListener to be called.
         */
        boolean onError(IVideoFramePlayer mp, int what, int extra);
    }

    /**
     * Register a callback to be invoked when an error has happened
     * during an asynchronous operation.
     *
     * @param listener the callback that will be run
     */
    public void setOnErrorListener(OnErrorListener listener)
    {
        mOnErrorListener = listener;
    }

    private OnErrorListener mOnErrorListener;
}
